package com.dake.excel.demo.dao.biz;


import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dake.excel.demo.dao.entity.EegRecordMock;
import com.dake.excel.demo.dao.mapper.EegRecordMockMapper;
import com.dake.excel.demo.model.EegRecordMockExcel;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;


/**
 * @author kezhihui
 * @createTime 2024/02/05 13:59
 */
@Slf4j
@Component
@AllArgsConstructor
public class EegRecordMockBiz extends ServiceImpl<EegRecordMockMapper, EegRecordMock> {


    private final EegRecordMockMapper eegRecordMockMapper;

    /**
     * 串行执行
     * 50万数据大约37秒时间
     * 集成脑电数据生成excel返回下载
     */
    public void generateExcel() {
        long startTime = System.currentTimeMillis();
        String filePath = "F://data//excel//" + System.currentTimeMillis() + ".xlsx";
        int batchNum = 1000;
        int total = (int) this.count();
        int current = 1;
        int index = 1;
        try (ExcelWriter excelWriter = EasyExcel.write(filePath, EegRecordMockExcel.class).build()) {
            // 这里注意 如果同一个sheet只要创建一次
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            while (total > 0) {
                int offset = (current - 1) * batchNum;
                batchNum = total > batchNum ? batchNum : total;
                long sqlStartTime = System.currentTimeMillis();
                List<EegRecordMock> data = eegRecordMockMapper.listForPage(offset, batchNum);
                long sqlEndTime = System.currentTimeMillis();
                log.warn("数据库查询耗时{}豪秒", (sqlEndTime - sqlStartTime));
                long excelStartTime = System.currentTimeMillis();
                excelWriter.write(data, writeSheet);
                log.warn("写入excel耗时{}毫秒", (System.currentTimeMillis() - excelStartTime));
                log.warn("第{}次写入{}条数据成功", index++, batchNum);
                total -= batchNum;
                current++;
            }

        }
        long endTime = System.currentTimeMillis();
        log.warn("生成excel总耗时{}秒", (endTime - startTime) / 1000);

    }

    /**
     * 50万数据大约30秒时间
     * easyExcel不支持并发写操作，比较耗时的是数据库查询
     * 可以开两个线程 1.数据库分页查询 2.监听数据，写入excel
     *
     * @throws InterruptedException
     */
    public void generateExcelAsync() throws InterruptedException {

        SynchronousQueue<List<EegRecordMock>> queue = new SynchronousQueue<>();
        String filePath = "F://data//excel//" + System.currentTimeMillis() + ".xlsx";
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        int batchNum0 = 1000;
        int total0 = (int) this.count();
        int num = total0 / batchNum0;
        num = total0 % batchNum0 == 0 ? num : num + 1;
        CountDownLatch countDownLatch = new CountDownLatch(num);
        long startTime = System.currentTimeMillis();

        executorService.submit(() -> {

            int batchNum = batchNum0;
            int total = total0;
            int current = 1;

            AtomicInteger index = new AtomicInteger(0);
            log.warn("进入生产任务--------------");
            while (total > 0) {
                int offset = (current - 1) * batchNum;
                batchNum = total > batchNum ? batchNum : total;
                int size = batchNum;

                List<EegRecordMock> data = eegRecordMockMapper.listForPage(offset, size);
                try {
                    queue.put(data);
                    log.warn("我第{}次生产数据", index.incrementAndGet());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                total -= batchNum;
                current++;
            }
            log.warn("结束生产任务-----------------");

        });

        executorService.submit(() -> {

            try (ExcelWriter excelWriter = EasyExcel.write(filePath, EegRecordMockExcel.class).build()) {
                WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
                log.warn("进入写入任务-----------------");
                try {
                    while (countDownLatch.getCount() > 0) {
                        List<EegRecordMock> data = queue.take();
                        excelWriter.write(data, writeSheet);
                        countDownLatch.countDown();
                        log.warn("我第{}次成功写入{}条数据", countDownLatch.getCount(), data.size());
                    }
                    log.warn("结束写入任务-----------------");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    log.error("写入出错： {}", e.getMessage());
                }
            }


        });


        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        log.warn("生成excel总耗时{}秒", (endTime - startTime) / 1000);

        // 睡眠三秒的作用是跑测试用例时候，以免最后一次的countdown唤醒主线程后，excelWriter还没有来得及关闭程序就退出了
        Thread.sleep(3000);

    }

    /**
     * 分页查询语句覆盖索引优化，50万数据大约16秒
     *
     * @throws InterruptedException
     */
    public void generateExcelAsyncAndIndexSql() throws InterruptedException {

        SynchronousQueue<List<EegRecordMock>> queue = new SynchronousQueue<>();
        String filePath = "F://data//excel//" + System.currentTimeMillis() + ".xlsx";
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        int batchNum0 = 1000;
        int total0 = (int) this.count();
        int num = total0 / batchNum0;
        num = total0 % batchNum0 == 0 ? num : num + 1;
        CountDownLatch countDownLatch = new CountDownLatch(num);
        long startTime = System.currentTimeMillis();

        executorService.submit(() -> {

            int batchNum = batchNum0;
            int total = total0;
            int current = 1;

            AtomicInteger index = new AtomicInteger(0);
            log.warn("进入生产任务--------------");
            while (total > 0) {
                int offset = (current - 1) * batchNum;
                batchNum = total > batchNum ? batchNum : total;
                int size = batchNum;

                List<EegRecordMock> data = eegRecordMockMapper.listForPageWithIndex(offset, size);
                try {
                    queue.put(data);
                    log.warn("我第{}次生产数据", index.incrementAndGet());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                total -= batchNum;
                current++;
            }
            log.warn("结束生产任务-----------------");

        });

        executorService.submit(() -> {

            try (ExcelWriter excelWriter = EasyExcel.write(filePath, EegRecordMockExcel.class).build()) {
                WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
                log.warn("进入写入任务-----------------");
                try {
                    while (countDownLatch.getCount() > 0) {
                        List<EegRecordMock> data = queue.take();
                        excelWriter.write(data, writeSheet);
                        countDownLatch.countDown();
                        log.warn("我第{}次成功写入{}条数据", countDownLatch.getCount(), data.size());
                    }
                    log.warn("结束写入任务-----------------");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    log.error("写入出错： {}", e.getMessage());
                }
            }


        });


        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        log.warn("生成excel总耗时{}秒", (endTime - startTime) / 1000);

        // 睡眠三秒的作用是跑测试用例时候，以免最后一次的countdown唤醒主线程后，excelWriter还没有来得及关闭程序就退出了
        Thread.sleep(3000);

    }

    /**
     * 使用id分页优化，前提条件id是有序的，50万条数据大约5秒
     *
     * @throws InterruptedException
     */
    public void generateExcelAsyncAndIdMax() throws InterruptedException {

        SynchronousQueue<List<EegRecordMock>> queue = new SynchronousQueue<>();
        String filePath = "F://data//excel//" + System.currentTimeMillis() + ".xlsx";
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        int batchNum0 = 1000;
        int total0 = (int) this.count();
        int num = total0 / batchNum0;
        num = total0 % batchNum0 == 0 ? num : num + 1;
        CountDownLatch countDownLatch = new CountDownLatch(num);
        long startTime = System.currentTimeMillis();

        executorService.submit(() -> {

            int batchNum = batchNum0;
            int total = total0;
            int current = 1;

            AtomicInteger index = new AtomicInteger(0);
            AtomicReference<Integer> idIndex = new AtomicReference<>(0);

            log.warn("进入生产任务--------------");
            while (total > 0) {
                int offset = (current - 1) * batchNum;
                batchNum = total > batchNum ? batchNum : total;
                int size = batchNum;
                int maxId = idIndex.get();
                List<EegRecordMock> data = eegRecordMockMapper.listBetweenId(maxId, size);
                idIndex.set(data.get(data.size() - 1).getId());
                try {
                    queue.put(data);
                    log.warn("我第{}次生产数据,最大id:{}", index.incrementAndGet(), maxId);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                total -= batchNum;
                current++;
            }
            log.warn("结束生产任务-----------------");

        });

        executorService.submit(() -> {

            try (ExcelWriter excelWriter = EasyExcel.write(filePath, EegRecordMockExcel.class).build()) {
                WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
                log.warn("进入写入任务-----------------");
                try {
                    while (countDownLatch.getCount() > 0) {
                        List<EegRecordMock> data = queue.take();
                        excelWriter.write(data, writeSheet);
                        countDownLatch.countDown();
                        log.warn("我第{}次成功写入{}条数据", countDownLatch.getCount(), data.size());
                    }
                    log.warn("结束写入任务-----------------");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (Exception e) {
                    log.error("写入出错： {}", e.getMessage());
                }
            }


        });


        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        log.warn("生成excel总耗时{}秒", (endTime - startTime) / 1000);

        // 睡眠三秒的作用是跑测试用例时候，以免最后一次的countdown唤醒主线程后，excelWriter还没有来得及关闭程序就退出了
        Thread.sleep(3000);

    }


}
